"""
The MIT License (MIT)
Copyright © 2022 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/performance-monitor-micropython
"""
from machine import SPI, Pin
import utime
import st7789
import gc
from random import choice
import math
from config import Config

DEBUG = False

def timed_function(f, *args, **kwargs):
	myname = str(f).split(' ')[1]
	def new_func(*args, **kwargs):
		if DEBUG:
			t = utime.ticks_us()
			result = f(*args, **kwargs)
			delta = utime.ticks_diff(utime.ticks_us(), t)
			print('Function {} | Time = {:6.3f}ms'.format(myname, delta/1000))
		else:
			result = f(*args, **kwargs)
		return result
	return new_func


class GameOfLife(object):
	SCALES = 6

	DEATH = 0
	ALIVE = 1
	PENDING_DEATH = 2
	PENDING_ALIVE = 3

	COLORS = [st7789.BLACK, st7789.GREEN, st7789.BLACK, st7789.GREEN]

	def __init__(self, display:st7789.ST7789):
		self.__display = display
		self.__world_width = int(display.width() / GameOfLife.SCALES)
		self.__world_height = int(display.height() / GameOfLife.SCALES)
		self.__world = bytearray()

		self.__initialize_world()
		gc.collect()

	@timed_function
	def __initialize_world(self):
		for _ in range(int(self.__world_height / GameOfLife.SCALES)):
			self.__world.extend(b''.join(choice((b'\x00', b'\x01')) for _ in range(self.__world_width)))
			self.__world.extend(b''.join(b'\x00' for _ in range(self.__world_width * (GameOfLife.SCALES - 1))))

		if len(self.__world) < self.__world_width * self.__world_height:
			self.__world.extend(b''.join(choice((b'\x00', b'\x01')) for _ in range(self.__world_width * self.__world_height - len(self.__world))))

		gc.collect()

	def __get_surround_cells(self, row, col):
		last_row = (row - 1) % self.__world_height
		next_row = (row + 1) % self.__world_height
		last_col = (col - 1) % self.__world_width
		next_col = (col + 1) % self.__world_width

		count = 0
		count += 0 if self.__world[last_row * self.__world_width + col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[next_row * self.__world_width + col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[row * self.__world_width + last_col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[row * self.__world_width + next_col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[last_row * self.__world_width + last_col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[last_row * self.__world_width + next_col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[next_row * self.__world_width + last_col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1
		count += 0 if self.__world[next_row * self.__world_width + next_col] in [GameOfLife.DEATH, GameOfLife.PENDING_ALIVE] else 1

		return count

	@timed_function
	def next_generation(self):
		self.__world = bytearray(bytes(self.__world).replace(bytes([GameOfLife.PENDING_ALIVE]), bytes([GameOfLife.ALIVE])).replace(bytes([GameOfLife.PENDING_DEATH]), bytes([GameOfLife.DEATH])))

		for index in range(len(self.__world)):
			row = math.floor(index / self.__world_width)
			col = index - (row * self.__world_width)
			cell_index = row * self.__world_width + col
			status_changed = False

			count = self.__get_surround_cells(row, col)
			# print(f'cell({row}, {col}): {count}')

			if self.__world[cell_index] == GameOfLife.ALIVE:
				if count < 2 or count > 3:
					self.__world[cell_index] = GameOfLife.PENDING_DEATH
					status_changed = True
			elif count == 3:
				self.__world[cell_index] = GameOfLife.PENDING_ALIVE
				status_changed = True

			if status_changed:
				self.__display.fill_rect(
					col * GameOfLife.SCALES,
					row * GameOfLife.SCALES,
					GameOfLife.SCALES, GameOfLife.SCALES,
					GameOfLife.COLORS[self.__world[cell_index]]
				)

				status_changed = False

	def start(self):
		while True:
			self.next_generation()


@timed_function
def initialize_display():
	spi = SPI(
		1,
		baudrate=40000000,
		polarity=1,
		sck=Pin(Config.Pins.SCL),
		mosi=Pin(Config.Pins.SDA)
	)

	display = st7789.ST7789(
		spi, 240, 240,
		reset=Pin(Config.Pins.RST, Pin.OUT),
		dc=Pin(Config.Pins.DC, Pin.OUT)
	)
	display.init()

	gc.collect()

	return display


if __name__ == '__main__':
	try:
		display = initialize_display()

		game = GameOfLife(display)
		game.start()
	except KeyboardInterrupt:
		from machine import reset
		reset()
